#!/usr/bin/env python2.5
# -*- coding: utf-8 -*-
###############################################################################

"""

This file is intended to be run from the project root.  It will create a
subdirectory containing the virtual environment, etc.

   TODO: use subprocess.call('',shell=True)

"""

from optparse import OptionParser
import os, sys

BRAVERY          = False
venvtarballurl   = "http://pypi.python.org/packages/source/v/virtualenv/virtualenv-1.0.tar.gz#md5=fb86aabdfc2033612b936cf08ad811ec"
exists           = os.path.exists
pwd              = os.getcwd()


# Shell helpers, etc
###############################################################################

def movetositepackages(f):
  """
      TODO: compute site packages from the interpretter instead of using wildcard.
  """
  shell('mv '+f+' ' + VENVDIRNAME+'/lib/python*/site-packages/; ')

def uncompresstgz_and_movetositepackages(tarfile,dirname,quiet=False):
  """
    tarfile :: path of the file to uncompress (will be uncompressed to <cwd>)
    dirname :: the name of the directory that will result from untarring.
  """
  uncompress_tgz(tarfile,quiet=quiet)
  movetositepackages(dirname)

def shellvenv(line,**kargs):
  """
  """
  shell("cd "+VENVDIRNAME+"; "+line, **kargs)

def shell(line,prompt=False,bravery=False,quiet=False):
  """
  """
  line = line.strip()
  if quiet: line+='> /dev/null 2>&1'
  if not line.endswith(';'): line+=';'

  report("EXEC: \n   \""+line+'"')
  if prompt: raw_input()
  result = os.system(line)
  if result!=0:
    if not bravery and not BRAVERY:
      errmsg = '\nthe last command seemed to encounter a problem. drop to shell? [y/n] > '
      q=None
      while q not in ['y','n']:
        q = raw_input(errmsg)
      if q=='y': os.system('bash')
  return result

def uncompress_tgz(f,quiet=True):
  """
    NOTE: surrounding f with quotes will cause the wildcards to fail.. watch out for spaces.
  """
  shell('tar -zxvf '+f,quiet=quiet)

# Base requirements
###############################################################################

def makeaspen():
  """ install the aspen webserver to the site-packages for the venv
  """
  uncompress_tgz('deps/aspen*.tar.gz')
  shell('cp -rf aspen* '+VENVDIRNAME+'/lib/python*/site-packages/')

def easyinstall_from_disk():
  """
    NOTE: Ignores hidden directories
    ASSUMPTION: <cwd>/deps/easyinstallables exists and contains everything we are interested in.
  """
  #report("\nEasy install for Pyro, Django, pyxmms\n" + "#"*80)

  #return [[f,shell(VENV_EASYINSTALL + ' ' + f,quiet=True)] for f in easy_install_files]
  EASY_INSTALLABLES_DIR = os.path.join(pwd,'deps','easyinstallables')
  easy_install_files = [os.path.join(EASY_INSTALLABLES_DIR, f) for f in os.listdir(EASY_INSTALLABLES_DIR) if not f.startswith('.')]
  return easyinstall_from_list(easy_install_files)

def easyinstall_from_list(LST):
  """
  """
  #report("\nEasy install for Pyro, Django, pyxmms\n" + "#"*80)
  #EASY_INSTALLABLES_DIR = os.path.join(pwd,'deps','easyinstallables')
  #easy_install_files = [os.path.join(EASY_INSTALLABLES_DIR, f) for f in os.listdir(EASY_INSTALLABLES_DIR) if not f.startswith('.')]
  #easy_install_files = [os.path.join(EASY_INSTALLABLES_DIR, f) for f in os.listdir(EASY_INSTALLABLES_DIR) if not f.startswith('.')]
  return [[f,shell(VENV_EASYINSTALL + ' ' + f,quiet=True)] for f in LST]


def setuptools():
  """ OBSOLETE? """
  return
  report("installing setuptools\n"+"#"*80)
  uncompress_tgz('deps/setuptools*tar.gz')
  shell(VENV_PYTHON+' setuptools*/setup.py install')

def install_silk():
  """ install the silk icon set

        TODO: make command line argument for this.
  """
  uncompress_tgz('deps/_data/static/img/silk.tar.gz',quiet=True)
  line = 'mv silk '+VENVDIRNAME+'/data/static/img/'
  if not exists(os.path.join(VENVDIRNAME+'/data/static/img/','silk')): shell(line)

def mktmpdir():
  """
  """
  report("adding temporary directory to virtual environment\n"+"#"*80)
  if not os.path.exists(VENV_TMPDIR):   shell('mkdir -p '+VENV_TMPDIR)

def mktmpfiles():
  """ usually things like pidfiles that you may need to touch """
  [shell('touch '+f) for f in [VENV_TMPDIR+'/pyro.pid',VENV_TMPDIR+'/aspen.pid']]

def bootstrapvenv():
  """ Bootstrap virtual environment """
  report("checking for virtual-env tarball")
  if exists("deps/virtualenv-1.0.tar.gz"): report("virtualenv tarball: OK")
  else:
    report("virtualenv tarball: not found\ngrabbing newest virtualenv")
    shell("cd deps; "+ "wget "+venvtarballurl+'; cd ..')
  uncompress_tgz('deps/virtualenv*.tar.gz')
  shell('mv virtualenv*/virtualenv.py ./')

  #HACK to prevent virtualenv from touching the internet.
  shell('rm -rf support-files; mkdir support-files; cp deps/setup*egg support-files')
  line  = 'python2.5 ./virtualenv.py -v '+VENVDIRNAME + '; '
  line += ' mv virtualenv.py ' + VENVDIRNAME + '/bin; '
  line += 'cp '+VENVDIRNAME+'/bin/python2.5 '+VENV_PYTHON+';'
  shell(line)

# Extend the base environment to be useful
###############################################################################
def local_bootstrap2wd():
  """
  """
  shell('cp ' + prj_bootstrap +' .')

def projsrc():
  """install project source"""
  report('making links now.')
  shell('ln -s '+pwd+ '/deps/_src '+VENVDIRNAME+'/lib/python2.5/site-packages/'+PROJNAME)
def projdata():
  report('making links now.')
  shell('ln -s '+pwd+'/deps/_data '+VENVDIRNAME+'/data')
  shell('ln -s '+pwd+'/deps/_data/templates '+VENVDIRNAME+'/templates')
  shell('ln -s '+pwd+'/deps/_data/static '+VENVDIRNAME+'/static')

def make_rootshell():
  """
  """
  shell('cd '+VENVDIRNAME+'; ./bin/make_rsh; cd ..;')

# Command line option handlers
###############################################################################

def unbootstrap():
  """
  """
  local_bootstrap2wd()
  #where the functionality is like to be idiosyncratic load two methods from project's local bootstrap
  #exec('from '+'_bootstrap import unbootstrap_files_to_remove,unbootstrap_local')
  execfile(site_bootstrap_file)
  tmp = [VENVDIRNAME, 'virtualenv*', 'setuptools*',
                                  'aspen*', '_boot*', 'silk','_bootstrap*'
                                  'deps/_data/img/silk', 'support-files',
                                  'django_evo*', 'djangolog*']
  from _bootstrap import unbootstrap_files_to_remove,unbootstrap_site
  unbootstrap_files_to_remove+= tmp
  unbootstrap_site(**globals())
  if unbootstrap_files_to_remove:
    for f in filter(exists, unbootstrap_files_to_remove): report("removing: "+f)
    if not BRAVERY:     raw_input('press enter to continue> ')
    [ shell('rm -rf '+f,bravery=True) for f in unbootstrap_files_to_remove if exists(f) and '*' not in f]

builtinTarballs = [ ['deps/batchadmin.tar.gz','batchadmin/'],
                    ['deps/djangologging.tar.gz','djangologging/'],
                    ['deps/django_evolution.tar.gz','django_evolution'] ]

def misctarballs(extra):
  """
  """
  for e in extra:
    if not isinstance(e,list) and len(e)==2:
      raise Exception,'bootstrap: bad list provided for misc. tarballs'
  data = builtinTarballs
  data += extra
  k = {'quiet':True }
  [ uncompresstgz_and_movetositepackages(*d,**k) for d in data ]

def bootstrap():
  """
  """
  if not exists(prj_bootstrap):
    print "you do not have a project-specific bootstrap file@"+prj_bootstrap
    sys.exit()
  bootstrapvenv()
  projdata()
  mktmpdir()
  mktmpfiles()
  projsrc()
  setuptools()

  from _bootstrap import misctarball_list, bootstrap_site,easy_install_list

  ei_results = easyinstall_from_disk()
  ei_results2 = easyinstall_from_list(easy_install_list)
  local_bootstrap2wd()
  install_silk()

  misctarballs(misctarball_list)
  makeaspen()

  cleanup()
  makeFailuresReport(ei_results,"Failures with easyinstall (from disk):")
  makeFailuresReport(ei_results2,"Failures with easyinstall (from list):")

  if LAUNCH: launch()


#  ei_failures_report = [ [os.path.sep.join(f.split(os.path.sep)[-2:]),r] for f,r in ei_results if r!=0 ]
#  if ei_failures_report:
#    report("\nFailures with easyinstall:")
#    for err in ei_failures_report: report("\t"+str(err[0]))

  # Where the functionality is like to be idiosyncratic load two methods from project's local bootstrap
  # TODO.. move this to the outside and change the exec above to reflect that

  bootstrap_site(**globals())


# Idiosyncratically
###############################################################################

def makeFailuresReport(results,label):
  some_failures_report = [ [os.path.sep.join(f.split(os.path.sep)[-2:]),r] for f,r in results if r!=0 ]
  if some_failures_report:
    report('\n' + label)
    for err in some_failures_report: report("\t"+str(err[0]))

def cleanup():
  """
    Files and directories to remove (in the current directory)

      NOTE: consider well the difference between "cleanup" and "unbootstrap"
  """
  report("\nCleaning up")
  shell('rm -r virtualenv* aspen* setuptools* support-files _bootstrap.pyc _bootstrap.py silk deps/_data/static/img/fugue/ deps/_data/static/img/silk',bravery=True,quiet=True)

def report(s):
  print 'bootstrap: ',s

def launch():
  report("launching in case it went well.")
  shell('cd ' + VENVDIRNAME+'; ./go')

# Code entry point
###############################################################################

#This name will be how the project refers to itself through-out the code..
PROJNAME = "_project"

VENVDIRNAME           = 'V'
VENV_PYTHON           = os.path.join(VENVDIRNAME, "bin","python")
VENV_TMPDIR           = os.path.join(VENVDIRNAME,"data","tmp")
VENV_EASYINSTALL      = os.path.join(VENVDIRNAME,"bin","easy_install")
LAUNCH                = False
VENV_DBFILE           = os.path.join(VENVDIRNAME,'data','project.db')
prj_bootstrap         = os.path.join('deps','_bootstrap.py')
bootstrapcontainer    = os.path.split(__file__)[0]
site_bootstrap_file   = os.path.join(bootstrapcontainer,'deps','_bootstrap.py')

if __name__=='__main__':
  parser = OptionParser()
  parser.add_option("-l", "--launch",
                    action="store_true", dest="launch", default=False,
                    help="launch project")
  parser.add_option("-u", "--unbootstrap",
                    action="store_true", dest="unbootstrap", default=False,
                    help="undo the actions of the bootstrapper")
  parser.add_option("-r", "--rebootstrap",
                    action="store_true", dest="rebootstrap", default=False,
                    help="undo the actions of the bootstrapper, start fresh")
  parser.add_option("-b", "--bravery",
                    action="store_true", dest="bravery", default=False,
                    help="do NOT offer to jump into shell on error")

  (options, args) = parser.parse_args()
  if options.launch:     LAUNCH=True
  if options.bravery:     BRAVERY=True
  if options.unbootstrap:       unbootstrap()
  elif options.rebootstrap:     unbootstrap(); bootstrap()
  else:                         bootstrap()
